#pragma once
/**
 * @file Util.hpp
 * @author 1102403684@qq.com
 * @brief
 * @version 0.1
 * @date 2024-10-21
 *
 * @copyright Copyright (c) 2024
 *
 */

#include <fstream>
#include <string>
#include <vector>
#include <unordered_map>
#include <sys/stat.h>
#include <iostream>

#include "../src/Buffer.hpp"

std::unordered_map<std::string, std::string> mime_msg = {
    {".aac", "audio/aac"},
    {".abw", "application/x-abiword"},
    {".arc", "application/x-freearc"},
    {".avi", "video/x-msvideo"},
    {".azw", "application/vnd.amazon.ebook"},
    {".bin", "application/octet-stream"},
    {".bmp", "image/bmp"},
    {".bz", "application/x-bzip"},
    {".bz2", "application/x-bzip2"},
    {".cda", "application/x-cdf"},
    {".csh", "application/x-csh"},
    {".css", "text/css"},
    {".csv", "text/csv"},
    {".doc", "application/msword"},
    {".docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"},
    {".eot", "application/vnd.ms-fontobject"},
    {".epub", "application/epub+zip"},
    {".gz", "application/gzip"},
    {".gif", "image/gif"},
    {".htm", "text/html"},
    {".html", "text/html"},
    {".ico", "image/vnd.microsoft.icon"},
    {".ics", "text/calendar"},
    {".jar", "application/java-archive"},
    {".jpeg", "image/jpeg"},
    {".jpg", "image/jpeg"},
    {".js", "text/javascript"},
    {".json", "application/json"},
    {".jsonld", "application/ld+json"},
    {".mid", "audio/midi"},
    {".midi", "audio/midi"},
    {".mjs", "text/javascript"},
    {".mp3", "audio/mpeg"},
    {".mp4", "video/mp4"},
    {".mpeg", "video/mpeg"},
    {".mpkg", "application/vnd.apple.installer+xml"},
    {".odp", "application/vnd.oasis.opendocument.presentation"},
    {".ods", "application/vnd.oasis.opendocument.spreadsheet"},
    {".odt", "application/vnd.oasis.opendocument.text"},
    {".oga", "audio/ogg"},
    {".ogv", "video/ogg"},
    {".ogx", "application/ogg"},
    {".opus", "audio/opus"},
    {".otf", "font/otf"},
    {".png", "image/png"},
    {".pdf", "application/pdf"},
    {".php", "application/x-httpd-php"},
    {".ppt", "application/vnd.ms-powerpoint"},
    {".pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation"},
    {".rar", "application/vnd.rar"},
    {".rtf", "application/rtf"},
    {".sh", "application/x-sh"},
    {".svg", "image/svg+xml"},
    {".swf", "application/x-shockwave-flash"},
    {".tar", "application/x-tar"},
    {".tif", "image/tiff"},
    {".tiff", "image/tiff"},
    {".ts", "video/mp2t"},
    {".ttf", "font/ttf"},
    {".txt", "text/plain"},
    {".vsd", "application/vnd.visio"},
    {".wav", "audio/wav"},
    {".weba", "audio/webm"},
    {".webm", "video/webm"},
    {".webp", "image/webp"},
    {".woff", "font/woff"},
    {".woff2", "font/woff2"},
    {".xhtml", "application/xhtml+xml"},
    {".xls", "application/vnd.ms-excel"},
    {".xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"},
    {".xml", "application/xml"},
    {".xul", "application/vnd.mozilla.xul+xml"},
    {".zip", "application/zip"},
    {".3gp", "video/3gpp"},
    {".3g2", "video/3gpp2"},
    {".7z", "application/x-7z-compressed"}};

std::unordered_map<int, std::string> statu_msg{
    {100, "继续"},
    {101, "切换协议"},
    {102, "处理中"},
    {103, "早期提示"},
    {200, "成功"},
    {201, "已创建"},
    {202, "已接受"},
    {203, "非授权信息"},
    {204, "无内容"},
    {205, "重置内容"},
    {206, "部分内容"},
    {207, "多状态"},
    {208, "已报告"},
    {226, "使用IM"},
    {300, "多种选择"},
    {301, "永久移动"},
    {302, "临时移动"},
    {303, "查看其他"},
    {304, "未修改"},
    {305, "使用代理"},
    {306, "未使用"},
    {307, "临时重定向"},
    {308, "永久重定向"},
    {400, "错误请求"},
    {401, "未授权"},
    {402, "需要付款"},
    {403, "禁止访问"},
    {404, "未找到"},
    {405, "方法不允许"},
    {406, "不接受"},
    {407, "需要代理身份验证"},
    {408, "请求超时"},
    {409, "冲突"},
    {410, "已删除"},
    {411, "需要有效长度"},
    {412, "前提条件失败"},
    {413, "请求实体过大"},
    {414, "请求URI过长"},
    {415, "不支持的媒体类型"},
    {416, "请求范围不符合要求"},
    {417, "预期失败"},
    {418, "我是茶壶"},
    {421, "误导请求"},
    {422, "不可处理的实体"},
    {423, "锁定"},
    {424, "依赖关系失败"},
    {425, "太早"},
    {426, "需要升级"},
    {428, "需要前提条件"},
    {429, "请求过多"},
    {431, "请求头字段太大"},
    {451, "因法律原因不可用"},
    {500, "服务器内部错误"},
    {501, "未实现"},
    {502, "错误网关"},
    {503, "服务不可用"},
    {504, "网关超时"},
    {505, "HTTP版本不支持"},
    {506, "变体也协商"},
    {507, "存储空间不足"},
    {508, "循环检测"},
    {510, "未扩展"},
    {511, "需要网络身份验证"}

};

class Utils
{
public:
    /**
     * @brief
     *
     * @param src 待分割的字符串
     * @param sep 以sep进行分割
     * @param arry 存放到arry数组中
     * @return size_t 返回实际分割的段数
     */
    static size_t spilt(const std::string src, const std::string &sep, std::vector<std::string> *arry)
    {
        int count = 0, start = 0;

        while (start < src.size())
        {
            size_t pos = src.find(sep, start);
            if (pos == std::string::npos)
            {
                arry->push_back(src.substr(start));
                count++;
                break;
            }
            // ,abc,
            if (pos == start)
            {
                // 当前子串为空
                start = pos + sep.size();
                continue;
            }

            arry->push_back(src.substr(start, pos - start));
            start = pos + sep.size();
            count++;
        }

        return count;
    }

    /**
     * @brief 去除str前后的空格以及换行
     *
     * @param str
     * @return std::string
     */
    static std::string trim(const std::string &str)
    {
        // x12xxx
        int start = str.find_first_not_of(" ");
        if (start == std::string::npos)
            return " "; // 如果为空或全是空格则直接返回""
        int end = str.find_last_not_of(" ");

        if (str.at(end) == '\n')
            end--;
        if (str.at(end) == '\r')
            end--;
        return str.substr(start, end - start + 1);
    }

    static bool read_file(const std::string &filename, std::string &buf)
    {
        std::ifstream ifs(filename, std::ios::binary);
        if (!ifs.is_open())
        {
            return false;
        }

        size_t fsize = 0;
        ifs.seekg(0, ifs.end);
        fsize = ifs.tellg();

        ifs.seekg(0, ifs.beg);

        buf.resize(fsize);
        ifs.read(&buf[0], fsize);

        if (!ifs.good())
        {
            ifs.close();
            return false;
        }
        return true;
    }

    static bool write_file(const std::string &filename, const std::string &buf)
    {
        // 将源文件内容截断为零
        std::ofstream ofs(filename, std::ios::binary | std::ios::trunc);
        if (!ofs.is_open())
        {
            return false;
        }

        ofs.write(buf.c_str(), buf.size());

        if (!ofs.good())
        {
            ofs.close();
            return false;
        }

        ofs.close();
        return true;
    }

    static std::string char_to_hex(char c)
    {
        std::string hex_string = "%";
        int high_nibble = (c >> 4) & 0xF;
        int low_nibble = c & 0xF;

        hex_string += high_nibble < 10 ? '0' + high_nibble : 'A' + (high_nibble - 10);
        hex_string += low_nibble < 10 ? '0' + low_nibble : 'A' + (low_nibble - 10);

        return hex_string;
    }

    /**
     * @brief 将特殊字符的特殊编码值 转化为两个16进制字符,格式为 %HH
     * - RFC3986规定: . - _ ~  字母 数字 等绝对不编码字符
     * - W3C规定: 空格要编码为 '+' 字符
     */
    static std::string url_encode(const std::string &url, bool is_convert_space_to_plus)
    {
        std::string res;
        for (auto &c : url)
        {
            if (c == '.' || c == '-' || c == '_' || c == '~' || isalnum(c))
            {
                res += c;
                continue;
            }
            if (c == ' ' && is_convert_space_to_plus)
            {
                res += "+";
                continue;
            }

            res += char_to_hex(c);
        }
        return res;
    }

    static char hex_to_str(char high, char low)
    {

        int highd = (high >= '0' && high <= '9') ? high - '0' : std::toupper(high) - 'A' + 10;
        int lowd = (low >= '0' && low <= '9') ? low - '0' : std::toupper(low) - 'A' + 10;

        return (highd << 4) + lowd;
    }

    static std::string url_decode(const std::string &url, bool is_convert_plus_to_space)
    {
        // 当遇到%时 将紧随其后的两个字符转化为数字 第一个数字左移4位 ＋ 第二个数字
        std::string decode;

        for (int i = 0; i < url.size(); i++)
        {
            if (url[i] == '%' && i + 2 < url.size())
            {
                char high = url[i + 1];
                char low = url[i + 2];
                decode += hex_to_str(high, low);
                i += 2;
            }
            else if (url[i] == '+' && is_convert_plus_to_space)
            {
                decode += ' ';
            }
            else
            {
                decode += url[i];
            }
        }

        return decode;
    }

    static std::string get_statu_describe(int status)
    {

        auto it = statu_msg.find(status);
        if (it != statu_msg.end())
        {
            return it->second;
        }
        return "未知状态码";
    }

    // 根据文件扩展名获取文件mime
    static std::string get_ext_mime(const std::string filename)
    {

        size_t pos = filename.find_last_of('.');
        if (pos == std::string::npos)
        {
            return "application/octet-stream"; // 二进制文件
        }

        auto it = mime_msg.find(filename.substr(pos));
        if (it == mime_msg.end())
        {
            return "application/octet-stream";
        }

        return it->second;
    }

    static bool is_dir(const std::string &filename)
    {
        struct stat st;
        int ret = stat(filename.c_str(), &st);
        if (ret < 0)
        {
            return false;
        }
        return S_ISDIR(st.st_mode);
    }

    static bool is_regular(const std::string &filename)
    {
        struct stat st;
        int ret = stat(filename.c_str(), &st);
        if (ret < 0)
        {
            return false;
        }
        return S_ISREG(st.st_mode);
    }

    // 资源路径的有效性判断
    //  /index.html  /是相对根目录 映射的是某个服务器上的子目录
    //  /../index.html 这里的..是不安全的
    static bool is_valid_path(const std::string &path)
    {
        std::vector<std::string> subdir;
        spilt(path, "/", &subdir);
        int level = 0;
        for (auto &dir : subdir)
        {
            if (dir == "..")
            {
                level--;
                if (level < 0)
                    return false;

                continue;
            }
            level++;
        }
        return true;
    }
};
